﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.TestManagement.Client;
using System.Diagnostics;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using System.Threading;

namespace TestPlanWordWriter.Common
{
    public class TestPlanInfoSimpleBuilder
    {
        public delegate void TestSuitesInfoProgressChangedEventHandler(object sender, TestSuitesInfoProgressChangedEventArgs args);
        public delegate void TestCasesInfoProgressChangedEventHandler(object sender, TestCasesInfoProgressChangedEventArgs args);
        public delegate void BeforeTestPlanProcessEventHandler(object sender, BeforeTestPlanProcessEventArgs args);
        public delegate void BeforeTestSuiteProcessEventHandler(object sender, BeforeTestSuiteProcessEventArgs args);
        public delegate void BeforeTestCaseProcessEventHandler(object sender, BeforeTestCaseProcessEventArgs args);

        public event TestSuitesInfoProgressChangedEventHandler TestSuitesProgressChanged;
        public event TestCasesInfoProgressChangedEventHandler TestCasesProgressChanged;
        public event BeforeTestPlanProcessEventHandler BeforeTestPlanProcess;
        public event BeforeTestSuiteProcessEventHandler BeforeTestSuiteProcess;
        public event BeforeTestCaseProcessEventHandler BeforeTestCaseProcess;

        public int TotalSuites { get; private set; }
        public int ProcessedSuites { get; private set; }
        public int TotalTestCases { get; private set; }
        public int ProcessedTestCases { get; private set; }

        public ITestManagementTeamProject TestProject { get; private set; }
        public TfsTeamProjectCollection TeamProjectCollection { get; private set; }

        public TestPlanInfoSimpleBuilder(TfsTeamProjectCollection tpc, string teamProjectName)
        {
            ITestManagementService test_service = (ITestManagementService)tpc.GetService(typeof(ITestManagementService));
            this.TestProject = test_service.GetTeamProject(teamProjectName);
            this.TeamProjectCollection = tpc;
        }

        public TestPlanInfo BuildPlanInfo(int testPlanId, CancellationToken cancelToken, TestResultsSettings resultsSettings)
        {
            this.ProcessedSuites = 0;
            this.TotalSuites = this.TestProject.TestSuites.Query("SELECT * FROM TestSuite WHERE PlanId = " + testPlanId.ToString()).Count;

            ITestPlan testPlan = this.TestProject.TestPlans.Find(testPlanId);

            OnBeforeTestPlanProcess(testPlan);

            TestSuiteInfo rootSuiteInfo = BuildTestSuiteInfoRecursive(testPlan.RootSuite, testPlan, cancelToken, resultsSettings);

            TestPlanInfo planInfo = new TestPlanInfo(testPlan, rootSuiteInfo);
            return planInfo;
        }

        private TestSuiteInfo BuildTestSuiteInfoRecursive(ITestSuiteBase testSuite, ITestPlan testPlan, CancellationToken cancelToken, TestResultsSettings resultsSettings)
        {
            OnBeforeTestSuiteProcess(testSuite);

            TestSuiteInfo info = new TestSuiteInfo();

            if (testSuite.TestSuiteType == TestSuiteType.RequirementTestSuite)
            {
                IRequirementTestSuite requirementSuite = testSuite as IRequirementTestSuite;

                int workItemId = requirementSuite.RequirementId;
                WorkItem userStory = TfsGateway.GetWorkItem(this.TeamProjectCollection, workItemId);

                info.Id = userStory.Id;
                info.Description = userStory.Description;
                info.Title = userStory.Title;
            }
            else
            {
                info.Id = testSuite.Id;
                info.Description = testSuite.Description;
                info.Title = testSuite.Title;
            }

            if (testSuite.TestSuiteType == TestSuiteType.StaticTestSuite)
            {
                IStaticTestSuite staticSuite = testSuite as IStaticTestSuite;
                foreach (ITestSuiteBase subSuite in staticSuite.SubSuites)
                {
                    TestSuiteInfo childSuiteinfo = BuildTestSuiteInfoRecursive(subSuite, testPlan, cancelToken, resultsSettings);
                    info.InnerSuites.Add(childSuiteinfo);
                }
            }

            info.TestCases = BuildTestCases(testSuite, testPlan, cancelToken, resultsSettings);

            OnTestSuitesProgressChanged();

            return info;
        }

        public List<TestCaseInfo> BuildTestCases(ITestSuiteBase testSuite, ITestPlan testPlan, CancellationToken cancelToken, TestResultsSettings resultsSettings)
        {
            Thread.Sleep(500);
            ITestSuiteEntryCollection testCases = testSuite.TestCases;

            this.ProcessedTestCases = 0;
            this.TotalTestCases = testCases.Count;

            List<TestCaseInfo> innerTestCases = new List<TestCaseInfo>();
            foreach (ITestSuiteEntry testSuiteEntry in testCases)
            {
                cancelToken.ThrowIfCancellationRequested();

                TestCaseInfo info = BuildTestCaseInfo(testSuiteEntry.TestCase, testSuite, testPlan, resultsSettings);
                innerTestCases.Add(info);

                OnTestCasesProgressChanged();
            }
            return innerTestCases;
        }

        private TestCaseInfo BuildTestCaseInfo(ITestCase testCase, ITestSuiteBase testSuite, ITestPlan testPlan, TestResultsSettings resultsSettings)
        {
            OnBeforeTestCaseProcess(testCase);

            List<TestStepInfo> testSteps = new List<TestStepInfo>();

            TestActionCollection actions = testCase.Actions;
            foreach (ITestAction action in actions)
            {
                FillTestStepsFromAction(testSteps, action);
            }

            List<TestPointInfo> testPoints = BuildTestPoints(testCase, testSuite, testPlan, resultsSettings);

            TestCaseInfo info = new TestCaseInfo(testCase, testSteps, testPoints);
            return info;
        }

        private List<TestPointInfo> BuildTestPoints(ITestCase testCase, ITestSuiteBase testSuite, ITestPlan testPlan, TestResultsSettings resultsSettings)
        {
            List<TestPointInfo> testPointsInfo = new List<TestPointInfo>();

            ITestPointCollection testPoints = testPlan.QueryTestPoints(string.Format(
                "SELECT * FROM TestPoint WHERE SuiteId = {0} AND TestCaseId = {1}", 
                testSuite.Id, 
                testCase.Id));

            foreach (ITestPoint testPoint in testPoints)
            {
                TestPointInfo info = new TestPointInfo();

                info.Id = testPoint.Id;
                info.Comment = testPoint.Comment;
                info.ConfigurationName = testPoint.ConfigurationName;

                FillTestResultsInfo(info.ResultsInfo, testPoint, testCase, testSuite, testPlan, resultsSettings);

                testPointsInfo.Add(info);
            }

            return testPointsInfo;
        }

        private void FillTestResultsInfo(List<TestResultInfo> resultsInfo, ITestPoint testPoint, ITestCase testCase, ITestSuiteBase testSuite, ITestPlan testPlan, TestResultsSettings resultsSettings)
        {
            if (resultsSettings.Options == TestResultsOptions.MostRecent)
            {
                if (testPoint.MostRecentResult != null)
                {
                    TestResultInfo mostRecentResult = new TestResultInfo(testPoint.MostRecentResult);
                    resultsInfo.Add(mostRecentResult);
                }
            }
            else if (resultsSettings.Options == TestResultsOptions.Build)
            {
                FillTestResultsInfoFromBuildSpecific(resultsInfo, testCase, testSuite, testPlan, resultsSettings);
            }
            else if (resultsSettings.Options == TestResultsOptions.AllResults)
            {
                FillTestResultsInfoFromAllResults(resultsInfo, testCase, testSuite, testPlan);
            }
        }

        private void FillTestResultsInfoFromAllResults(List<TestResultInfo> testPointsResults, ITestCase testCase, ITestSuiteBase testSuite, ITestPlan testPlan)
        {
            foreach (ITestPoint point in testPlan.QueryTestPoints(string.Format(
                "SELECT * FROM TestPoint WHERE SuiteId = {0} AND TestCaseId = {1}",
                testSuite.Id,
                testCase.Id)))
            {
                string resultQuery = string.Format(
                    "SELECT * FROM TestResult WHERE TestPointId = {0}",
                    point.Id);

                ITestCaseResultCollection results = this.TestProject.TestResults.Query(resultQuery);
                foreach (ITestCaseResult testResult in results)
                {
                    TestResultInfo resultInfo = new TestResultInfo(testResult);
                    testPointsResults.Add(resultInfo);
                }
            }
        }

        private void FillTestResultsInfoFromBuildSpecific(List<TestResultInfo> testPointsResults, ITestCase testCase, ITestSuiteBase testSuite, ITestPlan testPlan, TestResultsSettings resultsSettings)
        {
            //Check state
            if (string.IsNullOrEmpty(resultsSettings.SpecificBuildNumber))
            {
                throw new InvalidOperationException("resultsSettings.SpecificBuildNumber has not been assigned");
            }

            foreach (ITestPoint point in testPlan.QueryTestPoints(string.Format(
                "SELECT * FROM TestPoint WHERE SuiteId = {0} AND TestCaseId = {1}",
                testSuite.Id,
                testCase.Id)))
            {
                string runsQuery = string.Format(
                        "SELECT * FROM TestRun WHERE BuildNumber = '{0}'",
                        resultsSettings.SpecificBuildNumber);

                IEnumerable<ITestRun> testRuns = this.TestProject.TestRuns.Query(runsQuery);
                foreach (ITestRun testRun in testRuns)
                {
                    string resultQuery = string.Format(
                        "SELECT * FROM TestResult WHERE TestRunId = {0} AND TestPointId = {1}",
                        testRun.Id,
                        point.Id);

                    ITestCaseResultCollection results = this.TestProject.TestResults.Query(resultQuery);
                    foreach (ITestCaseResult testResult in results)
                    {
                        TestResultInfo resultInfo = new TestResultInfo(testResult);
                        testPointsResults.Add(resultInfo);
                    }
                }
            }
        }

        private void FillTestStepsFromAction(List<TestStepInfo> testSteps, ITestAction action)
        {
            if (action is ITestStep)
            {
                ITestStep step = action as ITestStep;
                TestStepInfo info = new TestStepInfo(step);
                testSteps.Add(info);
            }
            else if (action is ISharedStepReference)
            {
                //Recursive search
                ISharedStepReference sharedStepReference = action as ISharedStepReference;
                ISharedStep sharedStep = sharedStepReference.FindSharedStep();
                foreach (ITestAction sharedStepAction in sharedStep.Actions)
                {
                    FillTestStepsFromAction(testSteps, sharedStepAction);
                }
            }
        }

        private void OnTestSuitesProgressChanged()
        {
            this.ProcessedSuites++;
            if (TestSuitesProgressChanged != null)
            {
                TestSuitesInfoProgressChangedEventArgs args = new TestSuitesInfoProgressChangedEventArgs(this.ProcessedSuites, this.TotalSuites);
                TestSuitesProgressChanged(this, args);
            }
        }

        private void OnTestCasesProgressChanged()
        {
            this.ProcessedTestCases++;
            if (TestCasesProgressChanged != null)
            {
                TestCasesInfoProgressChangedEventArgs args = new TestCasesInfoProgressChangedEventArgs(this.ProcessedTestCases, this.TotalTestCases);
                TestCasesProgressChanged(this, args);
            }
        }

        private void OnBeforeTestPlanProcess(ITestPlan testPlan)
        {
            if (BeforeTestPlanProcess != null)
            {
                BeforeTestPlanProcessEventArgs args = new BeforeTestPlanProcessEventArgs(testPlan);
                BeforeTestPlanProcess(this, args);
            }
        }

        private void OnBeforeTestSuiteProcess(ITestSuiteBase testSuite)
        {
            if (BeforeTestSuiteProcess != null)
            {
                BeforeTestSuiteProcessEventArgs args = new BeforeTestSuiteProcessEventArgs(testSuite);
                BeforeTestSuiteProcess(this, args);
            }
        }

        private void OnBeforeTestCaseProcess(ITestCase testCase)
        {
            if (BeforeTestCaseProcess != null)
            {
                BeforeTestCaseProcessEventArgs args = new BeforeTestCaseProcessEventArgs(testCase);
                BeforeTestCaseProcess(this, args);
            }
        }
    }
}
